// SPDX-License-Identifier: GPL-2.0-or-later

#include "fqterm.h"
#include "common.h"
#include "fqterm_trace.h"
#include "fqterm_session.h"
#include "fqterm_buffer.h"
#include "fqterm_text_line.h"
#include "fqterm_telnet.h"
#include "fqterm_decode.h"
#include "fqterm_zmodem.h"

#ifdef HAVE_PYTHON
#include <Python.h>
#endif //HAVE_PYTHON

#ifndef WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif

#include <chrono>
#include <thread>

#include <QString>
#include <QTimer>
#include <QMutex>
#include <QRegExp>
#include <QtAlgorithms>
#include <QChar>
#include <QPair>
#include <QReadLocker>
#include <QWriteLocker>
#include <QReadWriteLock>
#include <QFileDialog>
#include <QFile>

namespace FQTerm {

const QString  FQTermSession::endOfUrl[] = {
	"ac","ad","ae","af","ag","ai","al","am","an","ao","aq","ar","as","at",
	"au","aw","az","ba","bb","bd","be","bf","bg","bh","bi","bj","bm","bn","bo","br",
	"bs","bt","bv","bw","by","bz","ca","cc","cd","cf","cg","ch","ci","ck","cl","cm",
	"cn","co","uk","com","cr","cs","cu","cv","cx","cy","cz","de","dj","dk","dm",
	"do","dz","ec","edu","ee","eg","eh","er","es","et","fi","fj","fk","fm","fo",
	"fr","ga","gd","ge","gf","gg","gh","gi","gl","gm","gn","gov","gp","gq","aero",
	"asia","biz","coop","eu","info","museum","name","pro","travel","gr","gs","gt",
	"gu","gw","gy","hk","hm","hn","hr","ht","hu","id","ie","il","im","in","int",
	"io","iq","ir","is","it","je","jm","jo","jp","ke","kg","kh","ki","km","kn","kp",
	"kr","kw","ky","kz","la","lb","lc","li","lk","lr","ls","lt","lu","lv","ly","ma",
	"mc","md","mg","mh","mil","mk","ml","mm","mn","mo","mp","mq","mr","ms","mt",
	"mu","mv","mw","mx","my","mz","na","nc","ne","net","nf","ng","ni","nl","no",
	"np","nr","nt","nu","nz","om","org","pa","pe","pf","pg","ph","pk","pl","pm",
	"pn","pr","ps","pt","pw","py","qa","re","ro","ru","rw","sa","sb","sc","sd","se",
	"sg","sh","si","sj","sk","sl","sm","sn","so","sr","sv","st","sy","sz","tc","td",
	"tf","tg","th","tj","tk","tm","tn","to","tp","tr","tt","tv","tw","tz","ua","ug",
	"uk","um","us","uy","uz","va","vc","ve","vg","vi","vn","vu","wf","ws","ye","yt",
	"yu","za","zm","zw"
};

const QString FQTermSession::protocolPrefix[] = {
	"http://", "https://", "mms://", "rstp://", "ftp://", "mailto:", "telnet://"
};

FQTermSession::FQTermSession(FQTermConfig *config, FQTermParam param) {
	reconnectRetry_ = 0;
	scriptListener_ = NULL;
	param_ = param;
	termBuffer_ = new FQTermBuffer(param_.numColumns_,
				       param_.numRows_,
				       param_.numScrollLines_,
				       param_.hostType_ == 0);

	if (param.protocolType_ == 0) {
		telnet_ = new FQTermTelnet(param_.virtualTermType_.toLatin1(),
					   param_.numRows_, param_.numColumns_, param.protocolType_, param.hostType_ );
	} else if (param.protocolType_ == 3) {
		telnet_ = new FQTermTelnet(param_.virtualTermType_.toLatin1(),
					   param_.numRows_, param_.numColumns_, param.protocolType_, param.hostType_ );
	} else {
#if defined(_NO_SSH_COMPILED)
    QMessageBox::warning(this,
                         "sorry",
                         "SSH support is not compiled, "
                         "vTerminal can only use Telnet!");
    telnet_ = new FQTermTelnet(param_.virtualTermType_.toUtf8(),
                               param_.numRows_,
                               param_.numColumns_,
                               param.protocolType_,
                               param.hostType_);
#else
		telnet_ = new FQTermTelnet(param_.virtualTermType_.toUtf8(),
					   param_.numRows_, param_.numColumns_, param.protocolType_ , param.hostType_ , 
					   param_.sshUserName_.toUtf8(),
					   param_.sshPassword_.toUtf8());
#endif
	}

	zmodem_ = new FQTermZmodem(config, telnet_, param.protocolType_, param.serverEncodingID_);
    decoder_ = new FQTermDecode(termBuffer_, param.serverEncodingID_);
	FQ_VERIFY(connect(decoder_, SIGNAL(enqReceived()), this, SLOT(onEnqReceived())));
	FQ_VERIFY(connect(decoder_, SIGNAL(onTitleSet(const QString&)), this, SIGNAL(onTitleSet(const QString&))));

	isConnected_ = false;
#ifndef _NO_SSH_COMPILED
	if (param.protocolType_ != 0) {
		isSSHLogining_ = true;
	} else {
		isSSHLogining_ = false;
	}
#else
	isSSHLogining_ = false;
#endif
	isTelnetLogining_ = false;
	isIdling_ = false;
	isSendingMessage_ = false;
	isMouseX11_ = false;

	idleTimer_ = new QTimer;
	autoReplyTimer_ = new QTimer;

	isLogging_ = false;
	logData = NULL;

	acThread_ = new ArticleCopyThread(*this, waitCondition_, bufferWriteLock_);

	FQ_VERIFY(connect(decoder_, SIGNAL(mouseMode(bool)),  this, SLOT(setMouseMode(bool))));
	FQ_VERIFY(connect(telnet_, SIGNAL(readyRead(int, int)), this, SLOT(readReady(int, int))));
	FQ_VERIFY(connect(telnet_, SIGNAL(TelnetState(int)), this, SLOT(changeTelnetState(int))));
	FQ_VERIFY(connect(telnet_, SIGNAL(errorMessage(QString)), this, SIGNAL(errorMessage(QString))));
	FQ_VERIFY(connect(telnet_, SIGNAL(requestUserPwd(QString*, QString*, bool*)), this, SIGNAL(requestUserPwd(QString*, QString*, bool*))));
	FQ_VERIFY(connect(telnet_, SIGNAL(warnInsecure(const QString &, bool*)), this, SIGNAL(warnInsecure(const QString &, bool*))));

	FQ_VERIFY(connect(telnet_, SIGNAL(onSSHAuthOK()), this, SLOT(onSSHAuthOK())));

	FQ_VERIFY(connect(termBuffer_, SIGNAL(onSetTermSize(int, int)), telnet_, SLOT(windowSizeChanged(int, int))));

	FQ_VERIFY(connect(zmodem_, SIGNAL(ZmodemState(int, int, const char *)), this, SIGNAL(zmodemStateChanged(int, int, const char *))));
	connect( zmodem_ , SIGNAL(ZmodemState(int, int, const char *)), this, SLOT(__on_zmodemStateChanged(int, int, const char *)));

	FQ_VERIFY(connect(idleTimer_, SIGNAL(timeout()), this, SLOT(onIdle())));
	FQ_VERIFY(connect(autoReplyTimer_, SIGNAL(timeout()), this, SLOT(onAutoReply())));

	FQ_VERIFY(connect(acThread_, SIGNAL(articleCopied(int, const QString)), this, SIGNAL(articleCopied(int, const QString))));

	setAntiIdle(param_.isAntiIdle_);
}

void FQTermSession::__on_zmodemStateChanged(int type, int value, const char *status)
{
	QString strMsg;
	//to be completed
	switch (type) {
	case RmtCancel:
		cancelZmodem();
	case FileEnd:
		//cancelZmodem();
		//writeStr( "clear\n" );
		break;
	default:
		break;
	}

}

FQTermSession::~FQTermSession() {
	delete idleTimer_;
	delete autoReplyTimer_;
	delete acThread_;
	delete termBuffer_;
	delete telnet_;
	delete zmodem_;
	delete decoder_;
}

void FQTermSession::setScreenStart(int nStart) {
	screenStartLineNumber_ = nStart;
}

bool FQTermSession::setCursorPos(const QPoint &pt, QRect &rc) {
	QRect rectOld = getMenuRect();

	cursorPoint_ = pt;

	detectMenuRect();
	QRect rectNew = getMenuRect();

	rc = rectOld | rectNew;

	return rectOld != rectNew;
}

QString FQTermSession::getMessage() {
	const FQTermTextLine *line;
	LineColorInfo colorInfo;
	QString message;

	getLineColorInfo(termBuffer_->getTextLineInTerm(0), &colorInfo);
	if (!colorInfo.uniBackgroundColor) {
		return message;
	}

	int i = 1;
	termBuffer_->getTextLineInTerm(0)->getAllPlainText(message);

	line = termBuffer_->getTextLineInTerm(i);
	getLineColorInfo(line, &colorInfo);
	while (colorInfo.uniBackgroundColor && colorInfo.hasBackgroundColor) {
		message += "\n";
		line->getAllPlainText(message);
		i++;
		line = termBuffer_->getTextLineInTerm(i);
		getLineColorInfo(line, &colorInfo);
	}
	return message;
}


void FQTermSession::detectPageState() {
	//for smth type bbs.
	pageState_ = Undefined;
	if (param_.hostType_ != 0) {
		return;
	}

	const FQTermTextLine *line[4];
	LineColorInfo colorInfo[4];
	int lineIndex[4] = {0, 1, 2};
	lineIndex[3] = termBuffer_->getNumRows() - 1;
	for (int i = 0; i < 4; ++i) {
		line[i] = termBuffer_->getTextLineInTerm(lineIndex[i]);
		getLineColorInfo(line[i], colorInfo + i);
	}

	//TODO: Detect PageState in a clearer way.
	if (!colorInfo[0].hasBackgroundColor) {
		if (colorInfo[3].foregroundColorIndex.count() != 0 &&
		    colorInfo[3].hasBackgroundColor){
			if (colorInfo[3].uniBackgroundColor) {
				QString text;
				line[3]->getAllPlainText(text);
				if (text[0] != L'\x3010') {
					pageState_ = Read;
				} else {
					pageState_ = Edit;
				}
			} else if (colorInfo[3].backgroundColorIndex.count() == 2 &&
				   colorInfo[3].backgroundColorIndex.at(0) == 4 &&
				   colorInfo[3].backgroundColorIndex.at(1) == 0){
				pageState_ = Read;
			}
		}
		return;
	}

	if (colorInfo[0].backgroundColorIndex.at(0) != 4
	    || !(colorInfo[3].hasBackgroundColor && colorInfo[3].backgroundColorIndex.at(0) == 4)) {
		if (!colorInfo[3].hasBackgroundColor &&
		    colorInfo[0].foregroundColorIndex.count() == 4 &&
		    colorInfo[0].foregroundColorIndex.at(1) == 4 &&
		    colorInfo[0].foregroundColorIndex.at(2) == 7 &&
		    colorInfo[0].foregroundColorIndex.at(3) == 4){
			pageState_ = TOP10;
		}
		return;
	}

	if (colorInfo[1].hasBackgroundColor && colorInfo[1].uniBackgroundColor) {
		pageState_ = Message;
		return;
	}

	if (colorInfo[0].uniBackgroundColor) {
		if (!colorInfo[2].hasBackgroundColor ||
		    colorInfo[2].backgroundColorIndex.at(0) != 4 ||
		    colorInfo[2].backgroundColorIndex.count() > 3) {
			pageState_ = Menu;
			return;
		}
	}

	if (!colorInfo[0].uniBackgroundColor &&
	    colorInfo[0].backgroundColorIndex.at(0) == 4 &&
	    !colorInfo[3].uniBackgroundColor) {
		//announce, elite root
		pageState_ = EliteArticleList;
		return;
	}

	if (colorInfo[2].hasBackgroundColor &&
	    !colorInfo[2].uniBackgroundColor &&
	    colorInfo[2].backgroundColorIndex.at(0) == 4 &&
	    !colorInfo[3].uniBackgroundColor) {
		pageState_ = EliteArticleList;
		return;
	}

	if (colorInfo[0].foregroundColorIndex.indexOf(14) != -1) {
		pageState_ = ArticleList;
		return;
	}

	if (colorInfo[2].foregroundColorIndex.indexOf(7) != -1) {
		QString text;
		line[2]->getAllPlainText(text);
		if (text[0] == ' ') {
			pageState_ = BoardList;
		} else {
			pageState_ = MailMenu;
		}
	}

}

FQTermSession::CursorType FQTermSession::getCursorType(const QPoint &pt)
{
	if (screenStartLineNumber_ !=
	    (termBuffer_->getNumLines() - termBuffer_->getNumRows())) {
		return kNormal;
	}

	QRect rc = getMenuRect();
	CursorType nCursorType = kNormal;
	switch (pageState_) {
	case Undefined:  // not recognized
		if (rc.contains(pt)) {
			//HAND
			nCursorType = kRight;
		} else {
			nCursorType = kNormal;
		}
		break;
	case Menu:
	case MailMenu:
		// menu
		if (pt.x() < 5) {
			// LEFT
			nCursorType = kLeft;
		} else if (rc.contains(pt)) {
			// HAND
			nCursorType = kRight;
		} else {
			nCursorType = kNormal;
		}
		break;
	case ArticleList:
	case BoardList:
	case EliteArticleList:
	case FriendMailList:
		// list
		if (pt.x() < 12) {
			// LEFT
			nCursorType = kLeft;
		} else if (pt.y() - screenStartLineNumber_ < 3) {
			// HOME
			nCursorType = kHome;
		} else if (pt.y() == termBuffer_->getNumLines() - 1) {
			// END
			nCursorType = kEnd;
		} else if (pt.x() > termBuffer_->getNumColumns() - 16 &&
			   pt.y() - screenStartLineNumber_ <= termBuffer_->getNumRows() / 2) {
			//PAGEUP
			nCursorType = kPageUp;
		} else if (pt.x() > termBuffer_->getNumColumns() - 16
			   && pt.y() - screenStartLineNumber_ >
				      termBuffer_->getNumRows() / 2) {
			// PAGEDOWN
			nCursorType = kPageDown;
		} else if (rc.contains(pt)) {
			// HAND
			nCursorType = kRight;
		} else {
			nCursorType = kNormal;
		}
		break;
	case Read:// read
		if (pt.x() < 12) {
			// LEFT
			nCursorType = kLeft;
		} else if (pt.x() > (termBuffer_->getNumColumns() - 16) &&
			   (pt.y() - screenStartLineNumber_)
				   <= termBuffer_->getNumRows() / 2) {
			// PAGEUP
			nCursorType = kPageUp;
		} else if (pt.x() > (termBuffer_->getNumColumns() - 16) &&
			   (pt.y() - screenStartLineNumber_)
				   > termBuffer_->getNumRows() / 2) {
			// PAGEDOWN
			nCursorType = kPageDown;
		} else if (rc.contains(pt)) {
			//HAND
			nCursorType = kRight;
		} else {
			nCursorType = kNormal;
		}
		break;
	case Edit:
		// TODO: add action for kEdit state.
		if (rc.contains(pt)) {
			//HAND
			nCursorType = kRight;
		} else {
			nCursorType = kNormal;
		}
		break;
	case TOP10:
		if (pt.x() < 12) {
			nCursorType = kLeft;
		} else if (rc.contains(pt)){
			nCursorType = kRight;
		}
		break;
	default:
		FQ_TRACE("error", 2) << "Error, wrong PageState.";
		break;
	}

	return nCursorType;
}

bool FQTermSession::isSelectedMenu(int line) {
	// TODO: possible performance issue
	QRect rect = getMenuRect();

	// nothing selected
	if (rect.isNull()) {
		return false;
	}

	return line >= rect.bottom() && line <= rect.top();
}

bool FQTermSession::isSelectedMenu(const QPoint &pt) {
	// TODO: possible performance issue
	QRect rect = getMenuRect();

	// nothing selected
	if (rect.isNull()) {
		return false;
	}

	return rect.contains(pt);
}

FQTermSession::PageState FQTermSession::getPageState()
{
	return pageState_;
}

char FQTermSession::getMenuChar()
{
	return menuChar_;
}

void FQTermSession::setMenuRect(int row, int col, int len) {
	menuRect_ = QRect(col, row, len, 1);
}

QRect FQTermSession::detectMenuRect() {
	QRect rect(0, 0, 0, 0);
	menuRect_ = rect;
	if (scriptListener_) 
	{
		bool res = scriptListener_->postScriptCallback(SFN_DETECT_MENU );
		if (res) return menuRect_;
	}
	// current screen scrolled
	if (screenStartLineNumber_ !=
	    (termBuffer_->getNumLines() - termBuffer_->getNumRows())) {
		return rect;
	}

	const FQTermTextLine *line;
	int menuStartLine = 8;
	switch (pageState_) {
	case Undefined: break;
	case MailMenu:
		menuStartLine = 7;
	case Menu:
		if (cursorPoint_.y() - screenStartLineNumber_ >= menuStartLine &&
		    cursorPoint_.x() > 5) {
			line = termBuffer_->getTextLineInBuffer(cursorPoint_.y());
			QString cstr;
			int cell_end = line->getCellEnd(qMin(cursorPoint_.x(),
							     (int)line->getWidth()));
			line->getPlainText(0, cell_end, cstr);

			int base = cstr.lastIndexOf("   ");
			if (base == -1) {
				base = 0;
			}

			QRegExp reg("[a-zA-Z0-9][).\\]]");
			char indexChar = cstr.indexOf(reg, base);
			if (indexChar != -1) {
				menuChar_ = cstr.at(indexChar).toLatin1();

				QString strTmp = cstr.left(cstr.indexOf(reg, base));
				int nMenuStart = get_str_width((const UTF16 *)strTmp.data(),
							       strTmp.size());
				if (cstr[indexChar - 1] == '(' || cstr[indexChar - 1] == '[') {
					nMenuStart--;
				}

				cstr.clear();
				line->getAllPlainText(cstr);


				reg = QRegExp("[^ ]");

				int nMenuBaseLength = 20;
				int cell_begin =
					line->getCellBegin(
						qMin(nMenuStart + nMenuBaseLength, (int)line->getWidth() - 1));
				int char_begin = line->getCharBegin(cell_begin);
				//last [^ ] until char_begin
				strTmp = cstr.left(cstr.lastIndexOf(reg, char_begin) + 1);

				int nMenuLength =
					get_str_width((const UTF16 *)strTmp.data(), strTmp.size())
					- nMenuStart;
				if (nMenuLength == nMenuBaseLength + 1) {
					strTmp = cstr.left(cstr.indexOf(" ", char_begin) + 1);
					nMenuLength =
						get_str_width((const UTF16 *)strTmp.data(), strTmp.size())
						- nMenuStart;  //base length is not enough
				}

				if (cursorPoint_.x() >= nMenuStart && cursorPoint_.x() <=
									      nMenuStart + nMenuLength) {
					rect.setX(nMenuStart);
					rect.setY(cursorPoint_.y());
					rect.setWidth(nMenuLength);
					rect.setHeight(1);
				}
			}
		}
		break;
	case ArticleList:
	case BoardList:
	case FriendMailList:
	case EliteArticleList:
		if ((cursorPoint_.y() - screenStartLineNumber_) >= 3
		    && (cursorPoint_.y() - screenStartLineNumber_)
			       < termBuffer_->getNumRows() -1
		    && cursorPoint_.x() >= 12
		    && cursorPoint_.x() <= termBuffer_->getNumColumns() - 16) {
			line = termBuffer_->getTextLineInBuffer(cursorPoint_.y());
			//QString str = line->getText();
			QString str;
			line->getAllPlainText(str);

			if (str.count(" ") != (int)str.length()) {
				rect.setX(0);
				rect.setY(cursorPoint_.y());
				rect.setWidth(line->getWidth());
				rect.setHeight(1);
			}
		}
		break;
	case Read:

		break;
	case Edit:
		break;
	case TOP10:
	{
		int ln = cursorPoint_.y() - screenStartLineNumber_;
		if (ln >= 3 && (ln & 1) && ln <= 21 &&
		    cursorPoint_.x() >= 12 &&
		    cursorPoint_.x() <= termBuffer_->getNumColumns() - 8){
			line = termBuffer_->getTextLineInBuffer(cursorPoint_.y());

			QString str;
			line->getAllPlainText(str);

			if (str.count(" ") != (int)str.length()) {
				if (ln == 21){
					menuChar_ = '0';
				} else {
					menuChar_ = ((ln - 1) >> 1) + '0';
				}

				rect.setX(12);
				rect.setY(cursorPoint_.y());
				rect.setWidth(line->getWidth() - 20);
				rect.setHeight(1);
			}
		}
	}
	break;
	default:
		break;
	}
	menuRect_ = rect;
	return rect;
}

bool FQTermSession::isIllChar(char ch) {
	static char illChars[] = ";'\"[]<>^";
	return ch > '~' || ch < '!' || strchr(illChars, ch) != NULL;
}

bool FQTermSession::isUrl(QRect &rcUrl, QRect &rcOld) {
	return checkUrl(rcUrl, rcOld, false);
}

bool FQTermSession::isIP(QRect &rcUrl, QRect &rcOld) {
	return checkUrl(rcUrl, rcOld, true);
}

QString FQTermSession::expandUrl(const QPoint& pt, QPair<int, int>& range)
{
	//  int at = pt.x();
	range.first = -1;
	range.second = -2;
	const FQTermTextLine *textLine = termBuffer_->getTextLineInBuffer(pt.y());
	if (textLine == NULL || pt.x() > (int)textLine->getWidth()) {
		return "";
	}

	QString text;
	textLine->getAllPlainText(text);

	int cell_begin = textLine->getCellBegin(pt.x());
	int at = textLine->getCharBegin(cell_begin);
	if (at >= text.length()) {
		return "";
	}

	int start;
	int end;
	for (start = at; start >= 0 && !isIllChar(text.at(start).toLatin1());
	     start--) {
	}
	start++;
	for (end = at; end < text.length() && !isIllChar(text.at(end).toLatin1());
	     end++) {
	}
	range.first = get_str_width((const UTF16 *)text.data(), start);
	range.second = get_str_width((const UTF16 *)text.data(), end);
	return text.mid(start, end - start);
}


bool FQTermSession::checkUrl(QRect &rcUrl, QRect &rcOld, bool checkIP)
{
	QPoint pt = cursorPoint_;
	int at = pt.x();
	rcOld = urlRect_;

	if (at > urlRect_.left() && at < urlRect_.right() && urlRect_.y() == pt.y()) {
		if ( (checkIP && !ip_.isEmpty()) ||
		    (!checkIP && !url_.isEmpty()) ) {
			rcUrl = urlRect_;
			return true;
		}

	}

	QPoint urlStartPoint;
	QPoint urlEndPoint;
	urlStartPoint_ = QPoint();
	urlEndPoint_ = QPoint();
	urlRect_ = QRect(0, 0, -1, -1);
	if (!checkIP) {
		url_.clear();
	} else {
		ip_.clear();
	}

	QString urlText;
	//phase 1: find all consecutive legal chars
	QPair<int, int> range;
	urlText = expandUrl(pt, range);
	if (range.first < 0 || range.first >= range.second) {
		return false;
	}


	QRect urlRect = QRect(range.first, pt.y(), range.second - range.first, 1);
	urlStartPoint = QPoint(range.first, pt.y());
	urlEndPoint = QPoint(range.second, pt.y());

	if (range.second == termBuffer_->getNumColumns()) {
		for (int i = pt.y() + 1; i < termBuffer_->getNumLines(); ++i) {
			QString lineText = expandUrl(QPoint(0, i), range);
			if (range.first == 0) {
				urlText += lineText;
				urlEndPoint.setY(urlEndPoint.y() + 1);
				urlEndPoint.setX(range.second);
				if (range.second == termBuffer_->getNumColumns()) {
					continue;
				}
			}
			break;
		}
	}

	//phase 2: find protocol prefix for url
	//if none, prefix to add is set to "mailto:" if '@' is found
	//otherwise, the prefix is "http://" by default.
	QString prefixToAdd;
	int protocolIndex;
	for (protocolIndex = 0; protocolIndex < ProtocolSupported; ++protocolIndex) {
		int begin = urlText.indexOf(protocolPrefix[protocolIndex],
					    Qt::CaseInsensitive);
		if (begin != -1 && begin <= at) {
			prefixToAdd = protocolPrefix[protocolIndex];
			urlText = urlText.mid(begin + protocolPrefix[protocolIndex].length());
			urlStartPoint.setX(urlStartPoint.x() + begin);
			break;
		}
	}
	if (protocolIndex == ProtocolSupported) {
		int begin = urlText.indexOf("://");
		if (begin != -1) {
			return false;
		}
		if (urlText.indexOf('@') != -1) {
			prefixToAdd = protocolPrefix[Mailto];
		} else {
			prefixToAdd = protocolPrefix[Http];
		}
	}

	//no point or duplicated points
	if (urlText.indexOf("..") != -1 || urlText.indexOf('.') == -1) {
		return false;
	}

	//phase 3: (for check ip) if there is a ':' before ip, there must be a '@'
	int host_begin = qMax(urlText.lastIndexOf('@') + 1, 0);
	int host_end = urlText.indexOf(':', host_begin);
	if (host_end == -1) {
		host_end = urlText.indexOf('/', host_begin);
	}
	if (host_end == -1) {
		host_end = urlText.length();
	}
	if (host_begin >= host_end) {
		return false;
	}
	if (checkIP && urlText[host_end - 1] == '*') {
		urlText[host_end - 1] = '1';
	}

	for (int index = 0; index < urlText.length() && urlText.at(index) != '/';
	     index++) {
		QChar cur(urlText.at(index));
		if (!cur.isLetterOrNumber() && !QString("-_~:@.").contains(cur)) {
			return false;
		}
	}

	QString newIP;
	newIP = urlText.mid(host_begin, host_end - host_begin);
	QStringList ipv4 = newIP.split(QLatin1String("."));
	bool isIPv4 = true;
	if (ipv4.count() != 4){
		isIPv4 = false;
	}
	foreach(QString domain, ipv4) {
		bool ok;
		int num = domain.toInt(&ok);
		//won't tolerant spaces.
		if (!ok || num < 0 || num > 255 || domain.length() > 3) {
			isIPv4 = false;
			break;
		}
	}

	if (!checkIP){
		QString lastName = ipv4.back();

		int lastCharIndex = lastName.lastIndexOf(QRegExp("[a-zA-Z]"));
		if (lastCharIndex == -1) {
			if (!isIPv4) {
				return false;
			} else if (urlText == newIP) {
				return false;
			}
		} else {
			const QString *begin = endOfUrl;
			const QString *end = endOfUrl + sizeof(endOfUrl) / sizeof(QString *);
			if (qFind(begin, end, lastName) == end) {
				return false;
			}
		}
		url_ = prefixToAdd + urlText;
	} else {
		if (!isIPv4) {
			return false;
		}
		ip_ = newIP;
	}

	urlRect_ = urlRect;
	rcUrl = urlRect;
	urlStartPoint_ = urlStartPoint;
	urlEndPoint_ = urlEndPoint;
	return true;
}

QString FQTermSession::getUrl()
{
	return url_;
}

QString FQTermSession::getIP()
{
	return ip_;
}

bool FQTermSession::isPageComplete()
{
	return termBuffer_->getCaretRow() == (termBuffer_->getNumRows() - 1) && termBuffer_->getCaretColumn() == (termBuffer_->getNumColumns() - 1);
}

bool FQTermSession::isAntiIdle()
{
	return param_.isAntiIdle_;
}

void FQTermSession::setAntiIdle(bool antiIdle)
{
	param_.isAntiIdle_ = antiIdle;

	// disabled
	if (!param_.isAntiIdle_ && idleTimer_->isActive()) {
		idleTimer_->stop();
	}

	// enabled
	if (param_.isAntiIdle_) {
		if (idleTimer_->isActive()) {
			idleTimer_->stop();
		}
		idleTimer_->start(param_.maxIdleSeconds_ *1000);
	}
}

void FQTermSession::setAutoReconnect(bool autoReconnect)
{
	param_.isAutoReconnect_ = autoReconnect;
	reconnectRetry_ = 0;
	if (autoReconnect && !isConnected_) {
		reconnectProcess();
	}
}


void FQTermSession::autoReplyMessage()
{
	if (autoReplyTimer_->isActive()) {
		autoReplyTimer_->stop();
	}
	if (pageState_ != Message) {
		return;
	}
	if (scriptListener_) 
	{
		bool res = scriptListener_->postScriptCallback(SFN_AUTO_REPLY);
		if (res) return;
	}
	QByteArray cstrTmp = param_.replyKeyCombination_.toLocal8Bit();
	QByteArray cstr = parseString(cstrTmp.isEmpty() ? QByteArray("^Z"): cstrTmp);
	//cstr += m_param.m_strAutoReply.toLocal8Bit();
	cstr += unicode2bbs(param_.autoReplyMessage_);
	cstr += '\n';
	telnet_->write(cstr, cstr.length());

	emit messageAutoReplied();
}

QByteArray FQTermSession::parseString(const QByteArray &cstr, int *len) {
	QByteArray parsed = "";

	if (len != 0) {
		*len = 0;
	}

	for (uint i = 0; (long)i < cstr.length(); i++) {
		if (cstr.at(i) == '^') {
			i++;
			if ((long)i < cstr.length()) {
				parsed += FQTERM_CTRL(cstr.at(i));
				if (len != 0) {
					*len =  *len + 1;
				}
			}
		} else if (cstr.at(i) == '\\') {
			i++;
			if ((long)i < cstr.length()) {
				if (cstr.at(i) == 'n') {
					parsed += CHAR_CR;
				} else if (cstr.at(i) == 'r') {
					parsed += CHAR_LF;
				} else if (cstr.at(i) == 't') {
					parsed += CHAR_TAB;
				}
				if (len != 0) {
					*len =  *len + 1;
				}
			}
		} else {
			parsed += cstr.at(i);
			if (len != 0) {
				*len =  *len + 1;
			}
		}
	}

	return parsed;
}

void FQTermSession::reconnect() {
	if (!isConnected_) {
		telnet_->connectHost(param_.hostAddress_, param_.port_);
		reconnectRetry_++;
	}
}

void FQTermSession::reconnectProcess() {
	if (!isConnected_ && param_.isAutoReconnect_ && (reconnectRetry_ < param_.retryTimes_ || param_.retryTimes_ == -1)) {
		int interval = param_.reconnectInterval_ <= 0 ? 0 : param_.reconnectInterval_ * 1000;
		QTimer::singleShot(interval, this, SLOT(reconnect()));
	}
}

void FQTermSession::setMouseMode(bool on) {
	isMouseX11_ = on;
}

void FQTermSession::doAutoLogin() {
	if (!param_.preLoginCommand_.isEmpty()) {
		QByteArray temp = parseString(param_.preLoginCommand_.toLatin1());
		telnet_->write((const char*)(temp), temp.length());
	}
	if (!param_.userName_.isEmpty()) {
		QByteArray temp = param_.userName_.toLocal8Bit();
		telnet_->write((const char*)(temp), temp.length());
		char ch = CHAR_CR;
		telnet_->write(&ch, 1);
	}
	if (!param_.password_.isEmpty()) {
		QByteArray temp = param_.password_.toLocal8Bit();
		telnet_->write((const char*)(temp), temp.length());
		char ch = CHAR_CR;
		telnet_->write(&ch, 1);
	}

// smth ignore continuous input, so sleep 1 sec :)
#if defined(_OS_WIN32_) || defined(Q_OS_WIN32)
	Sleep(1000);
#else
	sleep(1);
#endif

	if (!param_.postLoginCommand_.isEmpty()) {
		QByteArray temp = parseString(param_.postLoginCommand_.toLatin1());
		telnet_->write((const char*)(temp), temp.length());
	}
	isTelnetLogining_ = false;
}

//read slot
void FQTermSession::readReady(int size, int raw_size)
{
	// FQ_ASSERT(size > 0 || raw_size > 0);   maybe raw_size is greater than 0.

	raw_data_.resize(raw_size);
	telnet_->read_raw(&raw_data_[0], raw_size);

	if (isLogging_) {
		logData->append(&raw_data_[0], raw_size);
	}

	// read raw buffer
	int zmodem_consumed;
	if (param_.enableZmodem_)
		zmodem_->ZmodemRcv((uchar*)&raw_data_[0], raw_data_.size(), &(zmodem_->info), zmodem_consumed);

	if (zmodem_->transferstate == notransfer) {
		//decode
		if (size > 0 ) {
			int last_size = telnet_data_.size();
			telnet_data_.resize(last_size + size);
			telnet_->read(&telnet_data_[0] + last_size, size);
			int processed = 0;
			{
				while (!bufferWriteLock_.tryLockForWrite()) {}
				//QWriteLocker locker(&bufferWriteLock_);
				processed = decoder_->decode(&telnet_data_[0], telnet_data_.size());
				bufferWriteLock_.unlock();
			}
			telnet_data_.erase(telnet_data_.begin(), telnet_data_.begin() + processed);
		}

		if (isTelnetLogining_) {
			int n = termBuffer_->getCaretRow();
			for (int y = n - 5; y < n + 5; y++) {
				y = qMax(0, y);
				const FQTermTextLine *pTextLine = termBuffer_->getTextLineInTerm(y);
				if (pTextLine == NULL) {
					continue;
				}

				// QString str = pTextLine->getText();
				QString str;
				pTextLine->getAllPlainText(str);

				if (str.indexOf("guest") != -1 /*&& str.indexOf("new") != -1*/) {
					doAutoLogin();
					break;
				}
			}
		}
		// page complete when caret at the right corner
		// this works for most but not for all
		const FQTermTextLine *pTextLine = termBuffer_->getTextLineInTerm(termBuffer_->getNumRows() - 1);

		//QString strText = stripWhitespace(pTextLine->getText());
		// TODO_UTF16: fixme! performance issue!
		QString strText;
		pTextLine->getAllPlainText(strText);

		// TODO_UTF16: shall we disable trim?
		strText = strText.trimmed();

		if (termBuffer_->getCaretRow() == termBuffer_->getNumRows() - 1
		    && termBuffer_->getCaretColumn() >= (strText.length() - 1) / 2) {
			waitCondition_.wakeAll();
		}

		//QToolTip::remove(this, screen_->mapToRect(m_rcUrl));

		// message received
		// 03/19/2003. the caret position removed as a message judgement
		// because smth.org changed
		if (decoder_->bellReceive()) {
			emit startAlert();
			emit bellReceived();

			bool bellConsumed = false;
			if (scriptListener_) {
				bellConsumed = scriptListener_->postScriptCallback(SFN_ON_BELL);
			}

			if (isAutoReply() && !bellConsumed) {
				// TODO: save messages
				if (isIdling_) {
					autoReplyMessage();
				} else {
					autoReplyTimer_->start(param_.maxIdleSeconds_ *1000 / 2);
				}
			}
		}

		// set page state
		detectPageState();
		if (scriptListener_) {
			scriptListener_->postScriptCallback(SFN_DATA_EVENT);
		}
		emit sessionUpdated();
	}

	if (zmodem_->transferstate == transferstop) {
		zmodem_->transferstate = notransfer;
	}
}

/* 0-left 1-middle 2-right 3-relsase 4/5-wheel
 *
 */
//void FQTermWindow::sendMouseState( int num,
//    Qt::ButtonState btnstate, Qt::ButtonState keystate, const QPoint& pt )
void FQTermSession::sendMouseState(int num, Qt::KeyboardModifier btnstate, Qt::KeyboardModifier keystate, const QPoint &pt)
{
	/*
	  if(!m_bMouseX11)
	  return;

	  QPoint ptc = screen_->mapToChar(pt);

	  if(btnstate&Qt::LeftButton)
	  num = num==0?0:num;
	  else if(btnstate&Qt::MidButton)
	  num = num==0?1:num;
	  else if(btnstate&Qt::RightButton)
	  num = num==0?2:num;

	  int state = 0;
	  if(keystate&Qt::ShiftModifier)
	  state |= 0x04;
	  if(keystate&Qt::MetaModifier)
	  state |= 0x08;
	  if(keystate&Qt::ControlModifier)
	  state |= 0x10;

	  // normal buttons are passed as 0x20 + button,
	  // mouse wheel (buttons 4,5) as 0x5c + button
	  if(num>3) num += 0x3c;

	  char mouse[10];
	  sprintf(mouse, "\033[M%c%c%c",
	  num+state+0x20,
	  ptc.x()+1+0x20,
	  ptc.y()+1+0x20);
	  telnet_->write( mouse, strlen(mouse) );
	*/
}

void FQTermSession::cancelZmodem() {
	zmodem_->zmodemCancel();
}

void FQTermSession::disconnect() {
	telnet_->close();
	finalizeConnection();
}

void FQTermSession::finalizeConnection()
{
	if (isConnected_) {
		QString strMsg = "";
		strMsg += "\n\n\n\r\n\r";
		strMsg += "\x1b[17C\x1b[0m===========================================\n\r";
		strMsg += "\x1b[17C Connection Closed, Press \x1b[1;31;40mEnter\x1b[0m To Connect\n\r";
		strMsg += "\x1b[17C===========================================\n\r";
		strMsg += "\x1b[17C            Press \x1b[1;31;40mSpace\x1b[0m To Close\n\r";
		strMsg += "\x1b[17C===========================================\n\r";
		decoder_->decode(strMsg.toLatin1(), strMsg.length());
	}
	isConnected_ = false;

	if (autoReplyTimer_->isActive()) {
		autoReplyTimer_->stop();
	}

	if (idleTimer_->isActive()) {
		idleTimer_->stop();
	}

	if (isLogging_) {
		stopLogging(false);
	}

	emit connectionClosed();
}

// telnet state slot
void FQTermSession::changeTelnetState(int state) {
	switch (state) {
	case TSRESOLVING:
		break;
	case TSHOSTFOUND:
		break;
	case TSHOSTNOTFOUND:
		finalizeConnection();
		break;
	case TSCONNECTING:
		break;
	case TSHOSTCONNECTED:
		isConnected_ = true;
		reconnectRetry_ = 0;
		if (param_.isAutoLogin_ &&  param_.protocolType_==0) {
			isTelnetLogining_ = true;
		}

		break;
	case TSPROXYCONNECTED:
		break;
	case TSPROXYAUTH:
		break;
	case TSPROXYFAIL:
		disconnect();
		break;
	case TSREFUSED:
		finalizeConnection();
		break;
	case TSREADERROR:
		disconnect();
		break;
	case TSCLOSED:
		finalizeConnection();
		if (param_.isAutoReconnect_) {
			reconnectProcess();
		}
		break;
	case TSCLOSEFINISH:
		finalizeConnection();
		break;
	case TSCONNECTVIAPROXY:
		break;
	case TSEGETHOSTBYNAME:
		finalizeConnection();
		break;
	case TSEINIWINSOCK:
		finalizeConnection();
		break;
	case TSERROR:
		disconnect();
		break;
	case TSPROXYERROR:
		disconnect();
		break;
	case TSWRITED:
		// restart the idle timer
		if (idleTimer_->isActive()) {
			idleTimer_->stop();
		}
		if (param_.isAntiIdle_) {
			idleTimer_->start(param_.maxIdleSeconds_ *1000);
		}
		isIdling_ = false;
		break;
	default:
		break;
	}

	emit telnetStateChanged(state);
}

void FQTermSession::handleInput(const QString &text)
{
	if (text.length() > 0) {
		QByteArray cstrTmp = unicode2bbs_smart(text);
		telnet_->write(cstrTmp, cstrTmp.length());
	}
}

QString FQTermSession::bbs2unicode(const QByteArray &text)
{
	return encoding2unicode(text, param_.serverEncodingID_);
}

QByteArray FQTermSession::unicode2bbs(const QString &text) {
	return unicode2encoding(text, param_.serverEncodingID_);
}


QByteArray FQTermSession::unicode2bbs_smart(const QString &text)
{
	QByteArray strTmp;

	switch(param_.serverEncodingID_)
	{
	case FQTERM_ENCODING_GBK:
		if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_BIG5)
		{
			strTmp = U2B(text);
			char* tmp = encodingConverter_.B2G(strTmp, strTmp.length());
			strTmp = tmp;
			delete []tmp;
		}
		else
		{
			strTmp = U2G(text);
		}
		break;
	case FQTERM_ENCODING_BIG5:
		if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK)
		{
			strTmp = U2G(text);
			char* tmp = encodingConverter_.G2B(strTmp, strTmp.length());
			strTmp = tmp;
			delete []tmp;
		}
		else
		{
			strTmp = U2B(text);
		}
		break;
	case FQTERM_ENCODING_HKSCS:
		if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK)
		{
			strTmp = U2G(text);
			char* tmp = encodingConverter_.G2B(strTmp, strTmp.length());
			strTmp = tmp;
			delete []tmp;
		}
		else
		{
			strTmp = U2H(text);
		}
		break;
    case FQTERM_ENCODING_UTF8:
        strTmp = U2U8(text);
		break;
	case FQTERM_ENCODING_UAO:
		if (FQTermPref::getInstance()->imeEncodingID_ == FQTERM_ENCODING_GBK)
		{
			strTmp = U2G(text);
			char* tmp = encodingConverter_.G2B(strTmp, strTmp.length());
			strTmp = tmp;
			delete []tmp;
		}
		else
		{
			strTmp = U2A(text);
		}
		break;
	}

	return strTmp;
}


void FQTermSession::onIdle()
{
	// do as autoreply when it is enabled
	if (autoReplyTimer_->isActive() && param_.isAutoReply_) {
		autoReplyMessage();
		stopAlert();
		return ;
	}

	isIdling_ = true;
	// system script can handle that
	if (scriptListener_) {
		bool res = scriptListener_->postScriptCallback(SFN_ANTI_IDLE);
		if (res)
			return;
	}
	// the default function
	int length;
	QByteArray cstr = parseString(param_.antiIdleMessage_.toLocal8Bit(), &length);
	telnet_->write(cstr, length);
}

void FQTermSession::onAutoReply() {
	// if AutoReply still enabled, then autoreply
	if (param_.isAutoReply_) {
		autoReplyMessage();
	} else {
		// else just stop the timer
		autoReplyTimer_->stop();
	}

	stopAlert();
}
bool FQTermSession::isAutoReply()
{
	return param_.isAutoReply_;
}

void FQTermSession::setAutoReply(bool on) {
	param_.isAutoReply_ = on;
	if (!param_.isAutoReply_ && autoReplyTimer_->isActive()) {
		autoReplyTimer_->stop();
	}
}

int FQTermSession::write(const char *data, int len) {
	return telnet_->write(data, len);
}

int FQTermSession::writeStr(const char *str) {
	return write(str, strlen(str));
}

void FQTermSession::setProxy(int type, bool needAuth,const QString &hostName, quint16 portNumber, const QString &userName, const QString &password)
{
	telnet_->setProxy(type, needAuth, hostName, portNumber, userName, password);
}

void FQTermSession::connectHost(const QString &hostName, quint16 portNumber)
{
	telnet_->connectHost(hostName, portNumber);
}

void FQTermSession::close()
{
	telnet_->close();
	finalizeConnection();
}

void FQTermSession::clearLineChanged(int index)
{
	termBuffer_->clearLineChanged(index);
}

void FQTermSession::setLineAllChanged(int index)
{
	termBuffer_->setLineAllChanged(index);
}

void FQTermSession::setTermSize(int col, int row)
{
	termBuffer_->setTermSize(col, row);
	param_.numColumns_ = col;
	param_.numRows_ = row;
}

FQTermBuffer *FQTermSession::getBuffer() const
{
	return termBuffer_;
}

void FQTermSession::setSelect(const QPoint &pt1, const QPoint &pt2)
{
	termBuffer_->setSelect(pt1, pt2);
}

void FQTermSession::clearSelect()
{
	termBuffer_->clearSelect();
}

void FQTermSession::leaveIdle()
{
	if (autoReplyTimer_->isActive()) {
		autoReplyTimer_->stop();
	}

	if (idleTimer_->isActive()) {
		idleTimer_->stop();
	}

	if (param_.isAntiIdle_) {
		idleTimer_->start(param_.maxIdleSeconds_ * 1000);
	}
}

void FQTermSession::copyArticle()
{
	if (!acThread_->isRunning()) {
		acThread_->start();
	}
}

void FQTermSession::getLineColorInfo(const FQTermTextLine * line, LineColorInfo * colorInfo)
{
	colorInfo->backgroundColorIndex.clear();
	colorInfo->foregroundColorIndex.clear();
	colorInfo->hasBackgroundColor = false;
	colorInfo->uniBackgroundColor = true;
	colorInfo->uniForegroundColor = true;
	colorInfo->hasForegroundColor = false;

	if (!line || line->getWidth() == 0) {
		return;
	}

	const unsigned char *color = line->getColors();
	unsigned char background = GETBG(color[0]);
	unsigned char foreground = GETFG(color[0]);
	colorInfo->backgroundColorIndex.append(background);
	colorInfo->foregroundColorIndex.append(foreground);
	//for (int i = 0; i < color.length() / 2; i++) {
	// TODO_UTF16: why use color.length()/2 formerly?
	for (int i = 0; i < (int)line->getWidth(); i++) {
		if (GETBG(color[i]) != background) {
			colorInfo->uniBackgroundColor = false;
			background = GETBG(color[i]);
			colorInfo->backgroundColorIndex.append(background);
		}
		if (GETBG(color[i]) != GETBG(NO_COLOR)) {
			colorInfo->hasBackgroundColor = true;
		}
		if (GETFG(color[i]) != foreground) {
			colorInfo->uniForegroundColor = false;
			foreground = GETFG(color[i]);
			colorInfo->foregroundColorIndex.append(foreground);
		}
		if (GETFG(color[i]) != GETFG(NO_COLOR)) {
			colorInfo->hasForegroundColor = true;
		}
	}
}

bool FQTermSession::readyForInput()
{
	return telnet_->readyForInput();
}

void FQTermSession::onEnqReceived()
{
	if (FQTermPref::getInstance()->replyENQ_)
		telnet_->write(ANSWERBACK_MESSAGE, sizeof(ANSWERBACK_MESSAGE));
}

void FQTermSession::onSSHAuthOK() {
	isSSHLogining_ = false;
	//setTermSize(80, 24);
	//setTermSize(param_.numColumns_, param_.numRows_);
	telnet_->windowSizeChanged(param_.numColumns_, param_.numRows_);
}

void FQTermSession::updateSetting( const FQTermParam& p )
{
	bool toggleAutoReconnect = (p.isAutoReconnect_ != param_.isAutoReconnect_);
	param_ = p;
	setTermSize(p.numColumns_, p.numRows_);
	setAntiIdle(isAntiIdle());
	if (toggleAutoReconnect)
		setAutoReconnect(param_.isAutoReconnect_);
}

void FQTermSession::startLogging()
{
	logData = new QByteArray();
	if (logData!=NULL) {
		isLogging_ = true;
	}
	else {
		return;
	}
}

void FQTermSession::stopLogging(bool savedata)
{
	if (savedata) {
		QString fileName = QFileDialog::getSaveFileName(NULL, 
								tr("Save logged data to file"));
		if (!fileName.isNull()) {
			QFile f(fileName);
			if (f.open(QIODevice::WriteOnly)) {
				f.write(*logData);
				f.close();
			}
		}
	}
	delete logData;
	isLogging_ = false;
}

bool FQTermSession::isLogging()
{
	return isLogging_;
}

ArticleCopyThread::ArticleCopyThread(FQTermSession &bbs, QWaitCondition &waitCondition, QReadWriteLock &bufferLock)
    : session_(bbs),
      waitCondition_(waitCondition),
      lock_(bufferLock)
{
    FQ_VERIFY(connect(this, SIGNAL(writeSession(const QString&)), &session_, SLOT(handleInput(const QString&)),
			  Qt::QueuedConnection));
}

ArticleCopyThread::~ArticleCopyThread() {}

static void removeTrailSpace(QString &line) {
	for (int last_non_space = line.size() - 1;
	     last_non_space >= 0; --last_non_space) {
		QChar a = line.at(last_non_space);
		if (!a.isSpace()) {
			line.resize(last_non_space + 1);
			break;
		}

		if (last_non_space == 0) {
			line.resize(0);
		}
	}
}


///////////////////////////////////////
//analyze last line when copy article
static std::pair<int, int> ParseLastLine(QString str) {
	int startLine = -1;
	int endLine = -1;
	int infoStart = str.indexOf('(', str.indexOf('(') + 1);
	int infoEnd = str.indexOf(')', infoStart);
	int infoSplit = str.indexOf('-', infoStart);
	if (infoStart == -1 || infoEnd == -1 || infoSplit == -1) {
		return std::make_pair(-1, -1);
	}
	bool ok = true;
	startLine = str.mid(infoStart + 1, infoSplit - infoStart - 1).toInt(&ok);
	if (!ok) {
		return std::make_pair(-1, -1);
	}
	endLine = str.mid(infoSplit + 1, infoEnd - infoSplit - 1).toInt(&ok);
	if (!ok) {
		return std::make_pair(-1, -1);
	}
	return std::make_pair(startLine, endLine);
}
///////////////////////////////////////


void ArticleCopyThread::run() {
	QReadLocker locker(&lock_);

	typedef std::vector<QString> Page;
	const FQTermBuffer *buffer = session_.getBuffer();;
	Page page;

	QString firstPageLastLine;
	buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(firstPageLastLine);
	bool MultiPage = (firstPageLastLine.indexOf("%") != -1);
	if (!MultiPage) {
		emit writeSession("q");
		emit writeSession("\n");
		if (!waitCondition_.wait(&lock_, 10000)) {
			emit articleCopied(DAE_TIMEOUT, "");
			return;
		}
		buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(firstPageLastLine);
		MultiPage = (firstPageLastLine.indexOf("%") != -1);
	}

	if (MultiPage) {
		//session_.write("e", 1);
		emit writeSession("e");
		if (!waitCondition_.wait(&lock_, 10000)) {
			emit articleCopied(DAE_TIMEOUT, "");
			return;
		}
		QString LastPageLastLine;
		buffer->getTextLineInTerm(buffer->getNumRows() - 1)->getAllPlainText(LastPageLastLine);
		std::pair<int, int> range = ParseLastLine(LastPageLastLine);
		if (range.first == -1 || range.second == -1) {
			return;
		}
		page.resize(range.second);
		for (int i = range.first; i <= range.second; ++i) {
			QString strTemp;
			buffer->getTextLineInTerm(i - range.first)->getAllPlainText(strTemp);
			page[i - 1] = strTemp;
		}
		//session_.write("s", 1);
		emit writeSession("s");
		if (!waitCondition_.wait(&lock_, 10000)) {
			emit articleCopied(DAE_TIMEOUT, "");
			return;
		}
	}
	else {
		for (int i = 0; i < buffer->getNumRows() - 1; ++i) {
			QString strTemp;
			buffer->getTextLineInTerm(i)->getAllPlainText(strTemp);
			page.push_back(strTemp);
		}
	}

	while (MultiPage) {
		// the end of article
		int lineIndex = buffer->getNumRows() - 1;
		//QByteArray lastLine = buffer->getLineInScreen(lineIndex)->getText();
		QString lastLine;
		buffer->getTextLineInTerm(lineIndex)->getAllPlainText(lastLine);

		std::pair<int, int> range = ParseLastLine(lastLine);
		if (range.first == -1 || range.second == -1) {
			page.push_back(lastLine);
			break;
		}

		// copy a page of lines exclude the last line.
		for (int i = range.first; i <= range.second; ++i) {
			QString strTemp;
			buffer->getTextLineInTerm(i - range.first)->getAllPlainText(strTemp);
			page[i - 1] = strTemp;
		}


		// wait for next page.
		//session_.write(" ", 1);
		emit writeSession(" ");
		if (!waitCondition_.wait(&lock_, 10000)) {
			emit articleCopied(DAE_TIMEOUT, "");
			break;
		}
	}



	QString articleText;

	for (Page::iterator line = page.begin(); line != page.end(); ++line) {
		removeTrailSpace(*line);
		articleText += (*line);
		articleText += (OS_NEW_LINE);
	}

	//qApp->postEvent(pWin, new QCustomEvent(DAE_FINISH));
	emit articleCopied(DAE_FINISH, articleText);
}

}  // namespace FQTerm

#include "fqterm_session.moc"
